Skip to main content

Knapsack Problem

The Knapsack Problem is a classic optimization problem that can be efficiently solved using dynamic programming. The goal is to choose a subset of items with maximum total value, subject to a weight constraint on the total weight of the chosen items.

Mathematical Formulation (0/1 Knapsack)

Given:

  • nn items indexed from 1 to nn
  • wi1w_{i-1}: weight of item ii
  • vi1v_{i-1}: value of item ii
  • capcap: maximum weight capacity of the knapsack

Objective:

  • Maximize i=1nxivi1\sum_{i=1}^n x_i v_{i-1}

Subject to:

  • i=1nxiwi1cap\sum_{i=1}^n x_i w_{i-1} \leq cap
  • xi{0,1}x_i \in \{0, 1\} for each ii

Here, xix_i is a binary variable indicating whether item ii is included in the knapsack.

Dynamic Programming Approach

Define dp[i,c]dp[i, c] as the maximum value that can be achieved with the first ii items and a knapsack capacity of cc.

State Transition:

  • If item ii is not included.
dp[i,c]=dp[i1,c]dp[i, c] = dp[i-1, c]
  • If item ii is included (only if wi1cw_{i-1} \leq c). dp[i,c]=max(dp[i1,c],dp[i1,cwi1]+vi1)dp[i, c] = \max(dp[i-1, c], dp[i-1, c-w_{i-1}] + v_{i-1})

Initialization:

  • dp[0,c]=0dp[0, c] = 0 for all cc (No items means no value).

Algorithm

  1. Initialize a table dpdp with dimensions (n+1)×(cap+1)(n+1) \times (cap+1), all set to zero.
  2. For each item ii from 1 to nn:
    • For each capacity cc from 1 to capcap:
      • If wi1cw_{i-1} \leq c, set dp[i,c]=max(dp[i1,c],dp[i1,cwi1]+vi1)dp[i, c] = \max(dp[i-1, c], dp[i-1, c-w_{i-1}] + v_{i-1}).
      • Otherwise, set dp[i,c]=dp[i1,c]dp[i, c] = dp[i-1, c].
  3. The value dp[n,cap]dp[n, cap] will be the maximum value that fits into the knapsack.

Complexity

  • Time Complexity: O(n×cap)O(n \times cap), where nn is the number of items and capcap is the capacity.
  • Space Complexity: O(n×cap)O(n \times cap), although it can be reduced to O(cap)O(cap) using a 1-dimensional array if only the value is required, not the items themselves.

Implementation

A typical dynamic programming table fills in values iteratively for increasing item counts and capacities, ensuring all sub-problems are solved in a bottom-up manner.

The brute force approach explores every combination using recursion, evaluating the total value of all possible sets of items.

def knapsack_dfs(wgt, val, i, c):
if i == 0 or c == 0:
return 0
if wgt[i-1] > c:
return knapsack_dfs(wgt, val, i-1, c)
no = knapsack_dfs(wgt, val, i-1, c)
yes = knapsack_dfs(wgt, val, i-1, c-wgt[i-1]) + val[i-1]
return max(no, yes)

Enhances the brute force approach by storing results of sub-problems to avoid recomputation.

def knapsack_dfs_mem(wgt, val, mem, i, c):
if i == 0 or c == 0:
return 0
if mem[i][c] != -1:
return mem[i][c]
if wgt[i-1] > c:
return knapsack_dfs_mem(wgt, val, mem, i-1, c)
no = knapsack_dfs_mem(wgt, val, mem, i-1, c)
yes = knapsack_dfs_mem(wgt, val, mem, i-1, c-wgt[i-1]) + val[i-1]
mem[i][c] = max(no, yes)
return mem[i][c]

Space Optimization

The space complexity can be reduced by maintaining a single-dimensional array and updating values in reverse to prevent overwriting needed values for future states.

def knapsack_dp_comp(wgt, val, cap):
dp = [0] * (cap + 1)
for i in range(1, len(wgt) + 1):
for c in range(cap, 0, -1):
if wgt[i-1] <= c:
dp[c] = max(dp[c], dp[c-wgt[i-1]] + val[i-1])
return dp[cap]

Variants of the Knapsack Problem

  1. 0/1 Knapsack Problem: Each item can either be taken or left (cannot be broken into smaller pieces). You must decide whether to include each item in the knapsack.
  2. Fractional Knapsack Problem: Items can be divided into smaller parts. Thus, you can take fractions of items, which makes the problem easier and solvable by greedy algorithms.
  3. Bounded Knapsack Problem: Each item can be included multiple times, but only up to a certain limit.
  4. Unbounded Knapsack Problem: Each item can be taken multiple times without any limit.